#  Author: Roberto Cavada <cavada@irst.itc.it>
#
#  Copyright (c) 2005 by Roberto Cavada
#
#  pygtkmvc is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2 of the License, or (at your option) any later version.
#
#  pygtkmvc is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with this library; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
#
#  For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
#  or email to the author Roberto Cavada <cavada@irst.itc.it>.
#  Please report bugs to <cavada@irst.itc.it>.

import support.metaclasses
from support.wrappers import ObsWrapperBase
from observable import Signal


class Model (object):
    """This class is the application model base class.
    It handles a set of observable properties which you are interested
    in showing by one ore more view - via one or more observers of course.
    The mechanism is the following:
    1. You are interested in showing a set of model property, that you can
    declare in the __properties__ member map. 
    2. You define one or more observers that observe one or more properties
    you registered. When someone changes a property value the model notifies
    the changing to each listening controller. The property-observer[s]
    association is given by the implicit rule in observers method names: if
    you want the model notified the changing event of the property 'p'
    you must define the method called 'property_p_change_notification' in
    each listening observer class.
    Notice that tipically 'controllers' implement the observer pattern. 
    The notification method gets the
    emitting model, the old value for the property and the new one.
    Properties functionalities are automatically provided by the
    ObservablePropertyMeta meta-class."""

    __metaclass__  = support.metaclasses.ObservablePropertyMeta 
    __properties__ = {} # override this
    
    def __init__(self):
        object.__init__(self)
        self.__observers = []
        # keys are properties names, values are methods inside the observer:
        self.__value_notifications = {}
        self.__instance_notif_before = {}
        self.__instance_notif_after = {}
        self.__signal_notif = {}
        
        for key in (self.__properties__.keys() + self.__derived_properties__.keys()):
            self.register_property(key)
            pass        

        return

    def register_property(self, name):
        """Registers an existing property to be monitored, and sets up
        notifiers for notifications"""
        if not self.__value_notifications.has_key(name): 
            self.__value_notifications[name] = []
            pass
        
        # registers observable wrappers
        prop = getattr(self, "_prop_%s" % name)
            
        if isinstance(prop, ObsWrapperBase):
            prop.__set_model__(self, name)

            if isinstance(prop, Signal):
                if not self.__signal_notif.has_key(name):
                    self.__signal_notif[name] = []
                    pass
                pass
            else:
                if not self.__instance_notif_before.has_key(name):
                    self.__instance_notif_before[name] = []
                    pass
                if not self.__instance_notif_after.has_key(name):
                    self.__instance_notif_after[name] = []
                    pass
                pass
            pass
                
        return
    

    def register_observer(self, observer):
        if observer in self.__observers: return # not already registered
        
        self.__observers.append(observer)
        for key in (self.__properties__.keys() + self.__derived_properties__.keys()):
            self.__add_observer_notification(observer, key)
            pass
        
        return
            

    def unregister_observer(self, observer):
        if observer not in self.__observers: return

        for key in (self.__properties__.keys() + self.__derived_properties__.keys()):
            self.__remove_observer_notification(observer, key)
            pass
        
        self.__observers.remove(observer) 
        return


    def _reset_property_notification(self, prop_name):
        """Called when it has be done an assignment that changes the
        type of a property or the instance of the property has been
        changed to a different instance. In this case it must be
        unregistered and registered again"""

        self.register_property(prop_name)

        for observer in self.__observers:
            self.__remove_observer_notification(observer, prop_name)
            self.__add_observer_notification(observer, prop_name)
            pass
        return
    

    def __add_observer_notification(self, observer, prop_name):
        """Searches in the observer for any possible listener, and
        stores the notification methods to be called later"""

        method_name = "property_%s_value_change" % prop_name
        if hasattr(observer, method_name):
            method = getattr(observer, method_name)
            if method not in self.__value_notifications[prop_name]:
                list.append(self.__value_notifications[prop_name], method)
                pass
            pass

        # is it a signal?
        orig_prop = getattr(self, "_prop_%s" % prop_name)
        if isinstance(orig_prop, Signal):
            method_name = "property_%s_signal_emit" % prop_name
            if hasattr(observer, method_name):
                method = getattr(observer, method_name)
                if method not in self.__signal_notif[prop_name]:
                    list.append(self.__signal_notif[prop_name], method)
                    pass
                pass
            pass
        
        # is it an instance change notification type?
        elif isinstance(orig_prop, ObsWrapperBase):
            method_name = "property_%s_before_change" % prop_name
            if hasattr(observer, method_name):
                method = getattr(observer, method_name)
                if method not in self.__instance_notif_before[prop_name]:
                    list.append(self.__instance_notif_before[prop_name], method)
                    pass
                pass
            
            method_name = "property_%s_after_change" % prop_name
            if hasattr(observer, method_name):
                method = getattr(observer, method_name)
                if method not in self.__instance_notif_after[prop_name]:
                    list.append(self.__instance_notif_after[prop_name], method)
                    pass
                pass
            pass

        return

    
    def __remove_observer_notification(self, observer, prop_name):
        if self.__value_notifications.has_key(prop_name):
            method_name = "property_%s_value_change" % prop_name
            if hasattr(observer, method_name):
                method = getattr(observer, method_name)
                if method in self.__value_notifications[prop_name]:
                    self.__value_notifications[prop_name].remove(method)
                    pass
                pass
            pass


        orig_prop = getattr(self, "_prop_%s" % prop_name)
        # is it a signal?
        if isinstance(orig_prop, Signal):
            method_name = "property_%s_signal_emit" % prop_name
            if hasattr(observer, method_name):
                method = getattr(observer, method_name)
                if method in self.__signal_notif[prop_name]:
                    self.__signal_notif[prop_name].remove(method)
                    pass
                pass
            pass

        # is it an instance change notification type?
        elif isinstance(orig_prop, ObsWrapperBase):
            if self.__instance_notif_before.has_key(prop_name):
                method_name = "property_%s_before_change" % prop_name
                if hasattr(observer, method_name):
                    method = getattr(observer, method_name)
                    if method in self.__instance_notif_before[prop_name]:
                        self.__instance_notif_before[prop_name].remove(method)
                        pass
                    pass
                pass
            
            if self.__instance_notif_after.has_key(prop_name):
                method_name = "property_%s_after_change" % prop_name
                if hasattr(observer, method_name):
                    method = getattr(observer, method_name)
                    if method in self.__instance_notif_after[prop_name]:
                        self.__instance_notif_after[prop_name].remove(method)
                        pass
                    pass
                pass
            pass
            
        return 


    def __notify_observer__(self, observer, method, *args, **kwargs):
        """This can be overridden by derived class in order to call
        the method in a different manner (for example, in
        multithreading, or a rpc, etc.)  This implementation simply
        calls the given method with the given arguments"""
        return method(*args, **kwargs)
    

    # ---------- Notifiers:
    
    def notify_property_value_change(self, prop_name, old, new):
        assert(self.__value_notifications.has_key(prop_name))
        for method in self.__value_notifications[prop_name] :
            self.__notify_observer__(method.im_self, method,
                                     self, old, new) # notifies the change
            pass
        return                

    def notify_method_before_change(self, prop_name, instance, meth_name,
                                    args, kwargs):
        assert(self.__instance_notif_before.has_key(prop_name))
        for method in self.__instance_notif_before[prop_name] :
            self.__notify_observer__(method.im_self, method, self, instance,
                                     meth_name, args, kwargs) # notifies the change
            pass
        return                

    def notify_method_after_change(self, prop_name, instance, meth_name,
                                   res, args, kwargs):
        assert(self.__instance_notif_after.has_key(prop_name))
        for method in self.__instance_notif_after[prop_name] :
            self.__notify_observer__(method.im_self, method, self, instance,
                                     meth_name, res, args, kwargs) # notifies the change
            pass
        return

    def notify_signal_emit(self, prop_name, args, kwargs):
        assert(self.__signal_notif.has_key(prop_name))
        for method in self.__signal_notif[prop_name] :
            self.__notify_observer__(method.im_self, method, self,
                                     args, kwargs) # notifies the signal emit
            pass
        return
        

    pass # end of class Model
# ----------------------------------------------------------------------



import gtk
# ----------------------------------------------------------------------
class TreeStoreModel (Model, gtk.TreeStore):
    """Use this class as base class for your model derived by
    gtk.TreeStore"""
    __metaclass__  = support.metaclasses.ObservablePropertyGObjectMeta   
    
    def __init__(self, column_type, *args):
        Model.__init__(self)
        gtk.TreeStore.__init__(self, column_type, *args)
        return
    pass


# ----------------------------------------------------------------------
class ListStoreModel (Model, gtk.ListStore):
    """Use this class as base class for your model derived by
    gtk.ListStore"""
    __metaclass__  = support.metaclasses.ObservablePropertyGObjectMeta   
    
    def __init__(self, column_type, *args):
        Model.__init__(self)
        gtk.ListStore.__init__(self, column_type, *args)
        return
    pass
    

# ----------------------------------------------------------------------
class TextBufferModel (Model, gtk.TextBuffer):
    """Use this class as base class for your model derived by
    gtk.TextBuffer"""
    __metaclass__  = support.metaclasses.ObservablePropertyGObjectMeta   
    
    def __init__(self, table=None):
        Model.__init__(self)
        gtk.TextBuffer.__init__(self, table)
        return
    pass
    
